package com.qiangesoft.rdp.starter.mvc.response;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.qiangesoft.rdp.starter.mvc.config.MvcProperties;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.annotation.Annotation;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 返回结果包装
 *
 * @author qiangesoft
 * @date 2023-09-18
 */
@Slf4j
@RestControllerAdvice
public class RdpResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private MvcProperties mvcProperties;

    /**
     * 接口忽略包装的注解
     */
    private static final Class<? extends Annotation> ANNOTATION_TYPE = ResponseBodyIgnore.class;

    /**
     * 判断类或者方法是否使用了@ResponseBodyIgnore
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return !AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE) && !returnType.hasMethodAnnotation(ANNOTATION_TYPE);
    }

    /**
     * 当类或者方法使用了 @ResponseResultBody 就会调用这个方法
     */
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 配置接口忽略包装
        boolean ignorePath = this.isIgnore(request);
        if (ignorePath) {
            return body;
        }
        // 配置类忽略包装
        boolean ignoreClazz = this.isIgnore(returnType);
        if (ignoreClazz) {
            return body;
        }

        // 未拦截到的错误信息返回json
        Class<?> returnClass = returnType.getMethod().getReturnType();
        if (Objects.equals(returnClass, ResponseEntity.class) && body instanceof LinkedHashMap) {
            Map map = (LinkedHashMap) body;
            Integer status = (Integer) map.get("status");
            String error = (String) map.get("error");
            return ResponseInfo.fail(status, error);
        }

        // 如果返回类型是string，那么springmvc是直接返回的，此时需要手动转化为json
        if (body instanceof String) {
            return objectMapper.writeValueAsString(ResponseInfo.success(body));
        }

        // 防止重复包裹的问题出现
        if (body instanceof ResponseInfo) {
            return body;
        }

        return ResponseInfo.success(body);
    }

    /**
     * 是否忽略包装
     *
     * @param request
     * @return
     */
    private boolean isIgnore(ServerHttpRequest request) {
        String path = request.getURI().getPath();
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        List<String> bodyExcludePaths = mvcProperties.getBodyExcludePaths();
        for (String excludePath : bodyExcludePaths) {
            boolean match = antPathMatcher.match(excludePath, path);
            if (match) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否忽略包装
     *
     * @param returnType
     * @return
     */
    private boolean isIgnore(MethodParameter returnType) {
        Class clazz = returnType.getContainingClass();
        List<Class> bodyExcludeClasses = mvcProperties.getBodyExcludeClasses();
        for (Class excludeClazz : bodyExcludeClasses) {
            if (excludeClazz.equals(clazz)) {
                return true;
            }
        }
        return false;
    }
}
